package com.cats.bi.service.impl;

import com.cats.bi.config.ApplicationContextHelper;
import com.cats.bi.dao.CommMapper;
import com.cats.bi.dao.MetadataItemMapper;
import com.cats.bi.enums.*;
import com.cats.bi.exception.BiException;
import com.cats.bi.pojo.dto.*;
import com.cats.bi.pojo.model.MetadataItemModel;
import com.cats.bi.service.SqlAssemblyService;
import com.cats.bi.chartservice.ChartDataService;
import com.cats.bi.sqltool.basic.AbstractClause;
import com.cats.bi.sqltool.basic.Column;
import com.cats.bi.sqltool.basic.Select;
import com.cats.bi.sqltool.date.DateFormat;
import com.cats.bi.sqltool.function.*;
import com.cats.bi.sqltool.syntax.join.AbstractJoin;
import com.cats.bi.sqltool.syntax.join.LeftJoin;
import com.cats.bi.sqltool.syntax.join.RightJoin;
import com.cats.bi.sqltool.syntax.sort.AbstractSort;
import com.cats.bi.sqltool.syntax.sort.Asc;
import com.cats.bi.sqltool.syntax.sort.Desc;
import com.cats.bi.sqltool.utils.ColumnUtils;
import com.cats.echarts.utils.GsonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author duxiaobo
 * @date 2021/8/129:00 下午
 */
@Service
public class SqlAssemblyServiceImpl implements SqlAssemblyService {


    final String PREFIX = "_";

    @Autowired
    private MetadataItemMapper metadataItemMapper;

    @Autowired
    private CommMapper commMapper;

    @Override
    public Select AssemblySql(ViewDto viewDto) {
        Select select = new Select();
        // 拼装数据源
        Select dataSelect = assembleDataSource(viewDto.getDataSource());
        String dataName = getUUIDTableName();

        // 组装指标
        assembleMeasures(select, dataSelect, dataName, viewDto.getMeasures());

        // 组装维度
        assembleDimensions(select, dataSelect, dataName, viewDto.getDimensions());



        // 组装过滤条件
        AbstractClause generalClause = AbstractClause.eq(new Column("1"), 1);
        if (!CollectionUtils.isEmpty(viewDto.getConfigFilters())) {
            viewDto.getConfigFilters().forEach(filterDataDto -> {
                assembleWhere(generalClause, dataSelect, dataName, filterDataDto);
            });
        }
        select.where(generalClause);

        // 二次计算
        Select secondSelect = new Select();
        String tableName = getUUIDTableName();
        Select calculation = secondCalculation(select, secondSelect, tableName, viewDto);

        //Select findSelect = new Select();
//        String findName = getUUIDTableName();
//        Column column = new Column(calculation, findName, Column.ALL);
//        findSelect.add(column);

        // 结果过滤
        if (!CollectionUtils.isEmpty(viewDto.getResultFilters())) {
            // 拼where后
            AbstractClause clause = AbstractClause.eq(new Column("1"), 1);
            viewDto.getResultFilters().forEach(filterDataDto -> {
                if (FilterEnum.measure_result.equals(filterDataDto.getFilterEnum())) {
                    // 为初始数据增加指标
                    assembleMeasures(select, dataSelect, dataName, Arrays.asList(
                            new MeasureDto().setCalculation(filterDataDto.getCalculation())
                                    .setEntityKey(filterDataDto.getEntityKey())
                                    .setItemKey(filterDataDto.getItemKey())
                                    .setSecondCalculation(SecondCalculationEnum.none)
                                    .setDataEnum(DataEnum.number)
                    ));
                }
                assembleWhere(clause, calculation, tableName, filterDataDto);
            });

            calculation.where(clause);
        }


        // 排序
        AbstractSort sort = null;
        for (DimensionDto dimensionDto : viewDto.getDimensions()) {
            sort = assembleSort(dimensionDto, calculation, tableName, sort);
        }
        calculation.orderBy(sort);
        // limit
        if (!Objects.isNull(viewDto.getLimitCount())) {
            calculation.limit(viewDto.getLimitCount());
        }
        return calculation;
    }

    private Select secondCalculation(Select select, Select secondSelect, String tableName, ViewDto viewDto) {
        if (!CollectionUtils.isEmpty(viewDto.getDimensions())) {
            viewDto.getDimensions().forEach(dimensionDto -> {
                Column column = new Column(select, tableName, dimensionDto.getEntityKey() + PREFIX + dimensionDto.getItemKey());
                secondSelect.add(column.as(dimensionDto.getEntityKey() + PREFIX + dimensionDto.getItemKey()));
            });
        }
        if (!CollectionUtils.isEmpty(viewDto.getMeasures())) {
            assembleSecondMeasures(secondSelect, select, tableName, viewDto.getMeasures());
        }
        return secondSelect;
    }

    private AbstractSort assembleSort(DimensionDto dimension, Select dataSource, String dataName, AbstractSort sort) {
        if (Objects.isNull(dimension)) {
            return sort;
        }
        Column column;
        if (DataEnum.date.equals(dimension.getDataEnum())) {
            column = new DateFormat(dataName, dimension.getEntityKey() + PREFIX + dimension.getItemKey(), dimension.getFormat());
        } else {
            column = new Column(dataSource, dataName, dimension.getEntityKey() + PREFIX + dimension.getItemKey());
        }

        if (Objects.isNull(sort)) {
            if (SortEnum.desc.equals(dimension.getSort())) {
                sort = new Desc(column);
            } else {
                sort = new Asc(column);
            }
        } else {
            if (SortEnum.desc.equals(dimension.getSort())) {
                sort.desc(column);
            } else {
                sort.asc(column);
            }
        }

        return sort;
    }

    @Override
    public Select assembleDataSource(DataSourceDto dataSourceDto) {
        Select select = new Select();
        dataSourceDto.getNodes().forEach(nodeDto -> {
            select.from(nodeDto.getEntityKey());
            List<MetadataItemModel> nodeItems = metadataItemMapper.getItemsByEntityKey(nodeDto.getEntityKey());
            nodeItems.forEach(nodeItem -> select.add(new Column(nodeItem.getEntityKey(), nodeItem.getItemKey()).as(nodeItem.getEntityKey() + PREFIX + nodeItem.getItemKey())));
            nodeDto.getLinks().forEach(linkDto -> assembleJoin(select, linkDto));
        });
        AbstractClause generalClause = AbstractClause.eq(new Column("1"), 1);
        dataSourceDto.getFilters().forEach(filterDataDto -> assembleWhere(generalClause, filterDataDto));
        select.where(generalClause);
        return select;
    }

    private void assembleJoin(Select select, LinkDto linkDto) {
        AbstractJoin join;
        LinkEnum linkEnum = linkDto.getLinkEnum();
        switch (linkEnum) {
            case left:
                join = new LeftJoin(new Column(linkDto.getFromEntityKey(), linkDto.getFromItemKey()), new Column(linkDto.getToEntityKey(), linkDto.getToItemKey()));
                break;
            case right:
                join = new RightJoin(new Column(linkDto.getFromEntityKey(), linkDto.getFromItemKey()), new Column(linkDto.getToEntityKey(), linkDto.getToItemKey()));
                break;
            default:
                throw new BiException("没有符合的连接方法");
        }
        select.join(join);
        List<MetadataItemModel> fromItems = metadataItemMapper.getItemsByEntityKey(linkDto.getFromEntityKey());
        List<MetadataItemModel> toItems = metadataItemMapper.getItemsByEntityKey(linkDto.getToEntityKey());
        fromItems.forEach(fromItem -> select.add(new Column(fromItem.getEntityKey(), fromItem.getItemKey()).as(fromItem.getEntityKey() + PREFIX + fromItem.getItemKey())));
        toItems.forEach(toItem -> select.add(new Column(toItem.getEntityKey(), toItem.getItemKey()).as(toItem.getEntityKey() + PREFIX + toItem.getItemKey())));
    }

    @Override
    public Select assembleMeasures(Select select, Select dataSource, String dataName, List<MeasureDto> measureDtoList) {
        if (CollectionUtils.isEmpty(measureDtoList)) {
            return select;
        }
        // 组装指标
        measureDtoList.forEach(measureDto -> {
            select.add(getColumnByMeasure(dataSource, dataName, measureDto));
        });
        return select;
    }

    public Select assembleSecondMeasures(Select secondSelect, Select select, String tableName, List<MeasureDto> measureDtoList) {
        if (CollectionUtils.isEmpty(measureDtoList)) {
            return secondSelect;
        }
        measureDtoList.forEach(measureDto -> {
            secondSelect.add(secondCalculationColumn(select, tableName, measureDto));
        });
        return secondSelect;
    }

    private Column secondCalculationColumn(Select select, String tableName, MeasureDto measureDto) {

        SecondCalculationEnum secondCalculationEnum = measureDto.getSecondCalculation();
        Column column;
        StringBuilder format = new StringBuilder();
        String key = measureDto.getSecondCalculation().getType() + PREFIX + measureDto.getCalculation().getType() + PREFIX + measureDto.getEntityKey() + PREFIX + measureDto.getItemKey();
        String prefix = measureDto.getSecondCalculation().getType() + PREFIX + measureDto.getCalculation().getType() + PREFIX;
        switch (secondCalculationEnum) {
            case none:
                column = new Column(select,tableName,key);
                break;
            case hbz:
                column = new Lag(select,tableName,key,measureDto.getPartitionName(),measureDto.getOverOrder());
                break;
            case hbzz:
                column = ColumnUtils.getRingRatioGrowthValue(tableName+"."+key,new Lag(select,tableName,key,measureDto.getPartitionName(),measureDto.getOverOrder()));
                break;
            case hbzzl:
                column = ColumnUtils.getRingRatioGrowthPercentage(tableName+"."+key,new Lag(select,tableName,key,measureDto.getPartitionName(),measureDto.getOverOrder()));
                break;
            case zb:
                column = ColumnUtils.getSumPercentage(tableName+"."+key,measureDto.getPartitionName(),measureDto.getOverOrder());
                break;
            case rank_desc:
                column = new Rank(select,tableName,key,measureDto.getPartitionName(),SortEnum.desc.getType());
                break;
            case rank_asc:
                column = new Rank(select,tableName,key,measureDto.getPartitionName(),SortEnum.asc.getType());
                break;
            default:
                throw new BiException("不支持的二次计算类型");

        }
        Optional.of(column).ifPresent((Column col) -> col.nameAs(prefix + measureDto.getEntityKey() + PREFIX + measureDto.getItemKey()));
        return column;
    }

    private Column getColumnByMeasure(Select dataSource, String dataName, MeasureDto measureDto) {

        CalculationEnum calculationType = measureDto.getCalculation();
        Column column;
        String prefix = measureDto.getSecondCalculation().getType() + PREFIX + measureDto.getCalculation().getType() + PREFIX;
        switch (calculationType) {
            case sum:
                column = new Sum(dataSource, dataName, measureDto.getEntityKey() + PREFIX + measureDto.getItemKey());
                break;
            case avg:
                column = new Avg(dataSource, dataName, measureDto.getEntityKey() + PREFIX + measureDto.getItemKey());
                break;
            case count:
                column = new Count(dataSource, dataName, measureDto.getEntityKey() + PREFIX + measureDto.getItemKey());
                break;
            case count_distinct:
                column = new CountDistinct(dataSource, dataName, measureDto.getEntityKey() + PREFIX + measureDto.getItemKey());
                break;
            default:
                throw new BiException("不支持的函数类型");
        }
        Optional.of(column).ifPresent((Column col) -> col.nameAs(prefix + measureDto.getEntityKey() + PREFIX + measureDto.getItemKey()));
        return column;
    }

    @Override
    public Select assembleDimensions(Select select, Select dataSource, String dataName, List<DimensionDto> dimensionDtoList) {
        if (CollectionUtils.isEmpty(dimensionDtoList)) {
            return select;
        }
        List<DimensionDto> rowDimension = dimensionDtoList.stream().filter(dimensionDto -> DimensionEnum.row.equals(dimensionDto.getDimensionEnum())).collect(Collectors.toList());
        List<DimensionDto> colDimensions = dimensionDtoList.stream().filter(dimensionDto -> DimensionEnum.col.equals(dimensionDto.getDimensionEnum())).collect(Collectors.toList());
        // 行维度
        if (!CollectionUtils.isEmpty(rowDimension)) {
            rowDimension.forEach(rowDimen -> {
                Column column = getColumnByDimension(dataSource, dataName, rowDimen);
                select.groupBy(column);
                select.add(column.as(rowDimen.getEntityKey() + PREFIX + rowDimen.getItemKey()));
            });
        }
        // 列维度
        if (!CollectionUtils.isEmpty(colDimensions)) {
            colDimensions.forEach(colDimen -> {
                Column column = getColumnByDimension(dataSource, dataName, colDimen);
                select.groupBy(column);
                select.add(column.as(colDimen.getEntityKey() + PREFIX + colDimen.getItemKey()));
            });
        }
        return select;
    }

    private Column getColumnByDimension(Select dataSource, String dataName, DimensionDto dimen) {
        Column column;
        if (DataEnum.date.equals(dimen.getDataEnum())) {
            column = new DateFormat(dataSource,dataName, dimen.getEntityKey() + PREFIX + dimen.getItemKey(), dimen.getFormat());
        } else {
            column = new Column(dataSource, dataName, dimen.getEntityKey() + PREFIX + dimen.getItemKey());
        }
        return column;
    }

    @Override
    public String getUUIDTableName() {
        return PREFIX + UUID.randomUUID().toString().split("-")[1];
    }


    private void assembleWhere(AbstractClause generalClause, FilterDataDto filterDataDto) {
        if (RelationEnum.and.equals(filterDataDto.getRelation())) {
            generalClause.and(getAbstractClause(filterDataDto, null, null));
        } else {
            generalClause.or(getAbstractClause(filterDataDto, null, null));
        }
    }

    @Override
    public AbstractClause assembleWhere(AbstractClause generalClause, Select dataSource, String dataName, FilterDataDto filterDataDto) {
        if (Objects.isNull(filterDataDto)) {
            return generalClause;
        }
        if (RelationEnum.and.equals(filterDataDto.getRelation())) {
            generalClause.and(getAbstractClause(filterDataDto, dataSource, dataName));
        } else {
            generalClause.or(getAbstractClause(filterDataDto, dataSource, dataName));
        }
        return generalClause;
    }

    private AbstractClause getAbstractClause(FilterDataDto filterDataDto, Select dataSource, String dataName) {
        AbstractClause clause;
        Column column;
        if (Objects.isNull(dataSource) || Objects.isNull(dataName)) {
            column = new Column(filterDataDto.getEntityKey(), filterDataDto.getItemKey());
        } else {
            if (FilterEnum.measure_result.equals(filterDataDto.getFilterEnum())) {
                column = new Column(dataSource, dataName, SecondCalculationEnum.none.getType() + PREFIX + filterDataDto.getCalculation().getType() + PREFIX + filterDataDto.getEntityKey() + PREFIX + filterDataDto.getItemKey());
            } else {
                column = new Column(dataSource, dataName, filterDataDto.getEntityKey() + PREFIX + filterDataDto.getItemKey());
            }
        }
        OperatorEnum operatorEnum = filterDataDto.getOperator();
        List<String> list = GsonUtil.getGson().fromJson(filterDataDto.getValueData(), List.class);
        switch (operatorEnum) {
            case equal:
                clause = AbstractClause.eq(column, list.get(list.size() - 1));
                break;
            case in:
                clause = AbstractClause.in(column, list);
                break;
            case less_than:
                clause = AbstractClause.lessThan(column, list.get(list.size() - 1));
                break;
            case more_than:
                clause = AbstractClause.greaterThan(column, list.get(list.size() - 1));
                break;
            case less_than_or_equal:
                clause = AbstractClause.lessEqual(column, list.get(list.size() - 1));
                break;
            case more_than_or_equal:
                clause = AbstractClause.greaterEqual(column, list.get(list.size() - 1));
                break;
            case not_in:
                clause = AbstractClause.notIn(column, list);
                break;
            case between:
                clause = AbstractClause.between(column, list.get(list.size() - 2), list.get(list.size() - 1));
                break;
            case not_between:
                clause = AbstractClause.notBetween(column, list.get(list.size() - 2), list.get(list.size() - 1));
                break;
            case all_like:
                clause = AbstractClause.like(column, list.get(list.size() - 1));
                break;
            case front_like:
                clause = AbstractClause.startWith(column, list.get(list.size() - 1));
                break;
            case behind_like:
                clause = AbstractClause.endWith(column, list.get(list.size() - 1));
                break;
            default:
                throw new BiException("没有符合的操作符");
        }
        return clause;
    }

    @Override
    public ChartDataService getStrategy(String chartType) {
        String className = ChartEnum.valueOf(chartType).getClassName();
        return (ChartDataService) ApplicationContextHelper.getBean(className);
    }
}
